<?php
// $Id: Moduler.php 27 2010-11-16 12:49:54Z mjohnson60@gmail.com $
//
// $Author: mjohnson60@gmail.com $
// $Date: 2010-10-28 06:05:27 -0400 (Thu, 28 Oct 2010) $
//
// $Rev: 9 $
//

Loader::import('Module');

/**
 * Enter description here ...
 * @author markjohnson
 *
 */
class Moduler extends Object
{
	private static $instance = NULL;
	
	var $modules = array();
	var $module_objects = array();
	var $implementations = array();

	/**
	 * Enter description here ...
	 */
	public function __construct()
	{
		self::$instance = & $this;
	}

	/**
	 * Enter description here ...
	 * @return Ambiguous
	 */
	public static function &getInstance()
	{
		if (!isset(self::$instance))
		{
			new Moduler();
		}
		
		return self::$instance;
	}
	
	/**
	 * alter()
	 * 
	 * Enter description here ...
	 * 
	 * @param string $type
	 * @param unknown_type $data
	 * @param unknown_type $context1
	 * @param unknown_type $context2
	 */
	public static function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) 
	{
		if (is_array($data) && isset($data['__alter_by_ref'])) 
		{
			$by_ref_parameters = $data['__alter_by_ref'];
			unset($data['__alter_by_ref']);
		}
		
		$args = array(&$data);
		if (isset($by_ref_parameters)) 
		{
			$args = array_merge($args, $by_ref_parameters);
		}
		
		$additional_args = func_get_args();
		array_shift($additional_args);
		array_shift($additional_args);
		$args = array_merge($args, $additional_args);
		
		foreach (Moduler::moduleImplements($type . 'Alter') as $module) 
		{
			call_user_func_array(array($module, $type . 'Alter'), $args);
		}
	}

	/**
	 * Enter description here ...
	 * @param unknown_type $type
	 * @param unknown_type $module
	 * @return boolean
	 */
	public static function moduleInclude($type, $module)
	{
		$file = './' . Athena::getPath('module', $module) . '/' . $module . '.' . $type;
		
		if (is_file($file))
		{
			include_once($file);
		}
		else
		{
			return FALSE;
		}
	}

	/**
	 * Enter description here ...
	 * @param unknown_type $module
	 */
	public static function loadInstall($module)
	{
		include_once('./includes/install.inc');
		Moduler::moduleInclude('install', $module);
	}

	/**
	 * Enter description here ...
	 */
	public static function loadAll()
	{
		Moduler::moduleList(TRUE, FALSE);
		
		$_this =& Moduler::getInstance();
		foreach ($_this->modules as $module)
		{
			Athena::loadFile('module', $module);
			if (class_exists($module, FALSE))
			{
				$module_object = new $module();
				$_this->module_objects[$module_object->name()] = $module_object;
			}
		}
	}
	
	/**
	 * invokeStatic()
	 * 
	 * Enter description here ...
	 * 
	 * @return mixed|boolean
	 */
	public static function invokeStatic()
	{
		$args = func_get_args();
		$module = array_shift($args);
		$model = array_shift($args);
		
		if (method_exists($module, $model))
		{
			return call_user_func_array(array($module, $model), $args);
		}
		else 
		{
			return FALSE;
		}
	}

	/**
	 * Enter description here ...
	 * @return Ambigous <multitype:, mixed>
	 */
	public static function invokeAll()
	{
		$args = func_get_args();
		$hook = array_shift($args);
		$return = array();
		foreach (Moduler::moduleImplements($hook) as $module)
		{
			$function = $module . '_' . $hook;
			$result = call_user_func_array($function, $args);
			if (isset($result) && is_array($result))
			{
				$return = array_merge_recursive($return, $result);
			}
			else if (isset($result))
			{
				$return[] = $result;
			}
		}
		
		return $return;
	}

	/**
	 * Enter description here ...
	 * @param unknown_type $module
	 * @param unknown_type $model
	 * @return boolean
	 */
	public static function model($module, $model)
	{
		return function_exists($module . '_' . $model);
	}

	/**
	 * Enter description here ...
	 * @param unknown_type $refresh
	 * @param unknown_type $initialize
	 * @param unknown_type $fixed_list
	 * @return Ambigous <multitype:, unknown>
	 */
	public static function moduleList($refresh = FALSE, $initialize = FALSE, $fixed_list = FALSE)
	{
		$_this =& Moduler::getInstance();
		$list = $_this->modules;
		
		if (empty($list) || $refresh || $fixed_list)
		{	
			$list = array();
			if ($fixed_list)
			{
				foreach ($fixed_list as $name => $module)
				{
					Athena::getFilename('module', $name, $module['filename']);
					$list[$name] = $name;
				}
			}
			else
			{
				if ($initialize)
				{
					$result = Db::query("SELECT name, filename FROM system WHERE type='module' AND status = 1 AND initialize = 1 ORDER BY weight ASC, filename ASC");
				}
				else
				{
					$result = Db::query("SELECT name, filename FROM system WHERE type='module' AND status = 1 ORDER BY weight ASC, filename ASC");
				}
				
				while (($module = Db::fetchObject($result)) !== FALSE)
				{
					if (file_exists($module->filename))
					{
						Athena::getFilename('module', $module->name, $module->filename);
						$list[$module->name] = $module->name;
					}
				}
				
				$_this->modules = $list;
			}
		}
		
		return $list;
	}

	/**
	 * Enter description here ...
	 * @param unknown_type $hook
	 * @param unknown_type $sort
	 * @param unknown_type $refresh
	 * @return void|array
	 */
	public static function moduleImplements($hook, $sort = FALSE, $refresh = FALSE)
	{
		$_this =& Moduler::getInstance();
		if ($refresh)
		{
			$_this->implementations = array();
			return;
		}
		
		if (!isset($_this->implementations[$hook]))
		{
			$_this->implementations[$hook] = array();
			
			foreach ($_this->module_objects as $name => $module)
			{
				if ($module->hooks($hook))
				{
					$_this->implementations[$hook][] = $name;
				}
			}
		}
		
		return (array) $_this->implementations[$hook];
	}
	
	/**
	 * Enter description here ...
	 * 
	 * @param unknown_type $module_name
	 */
	public static function moduleForName($module_name) 
	{
		$_this =& Moduler::getInstance();
		return $_this->module_objects[$module_name];
	}
	
	/**
	 * install()
	 * 
	 * Enter description here ...
	 * 
	 * @param string $module
	 * @return boolean
	 */
	public static function install($module) 
	{
		if (!empty($module)) 
		{
			$module_path = dirname(Athena::getFilename('module', $module));
			
			$installer = '';
			$installer = Renderer::methodForModel($module) . 'Installer';
			
			require('./' . $module_path . '/' . $module . '.install');
			
			if (athena_class_exists($installer)) 
			{
				$module_installer = new $installer();
				
				$module_installer->install();
				
				Db::query("INSERT INTO system (filename, name, type, owner, status, version, initialize, weight) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d, %d)", $module_path . '/' . $module . '.module', $module, 'module', '', 1, $module_installer->version, $module_installer->initialize, $module_installer->weight);
				
				Moduler::rebuildCache();
				
				return TRUE;
			}
		}
		
		return FALSE;
	}
	
	/**
	 * rebuildCache()
	 * 
	 * Enter description here ...
	 * 
	 * @return array
	 */
	public static function rebuildCache() 
	{
		$files = Athena::systemListing('\.module$', 'modules', 'name', 0);
		System::getFilesDatabase($files, 'module');
		
		ksort($files);
		
		foreach ($files as $filename => $file) 
		{
			$file->info = parse_info_file(dirname($file->filename) . '/' . $file->name . '.info', $file->name);
			
			if (empty($file->info)) 
			{
				unset($files[$filename]);
				continue;
			}
			
			Moduler::alter('system_info', $files[$filename]->info, $files[$filename]);
			
			$initialize = 0;
			foreach (Initializer::hooks() as $hook) 
			{
				if (Moduler::moduleHooks($file->name, $hook)) 
				{
					$initialize = 1;
					break;
				}
			}
			
			if (isset($file->status) || (isset($file->old_filename) && $file->old_filename != $file->filename)) 
			{
				Db::query("UPDATE system SET info = %b, name = '%s', filename = '%s', initialize = %d WHERE filename = '%s'", serialize($files[$filename]->info), $file->name, $file->filename, $initialize, $file->old_filename);
			}
			else 
			{
				$files[$filename]->status = 0;
				Db::query("INSERT INTO system (name, info, type, filename, status, initialize) VALUES ('%s', %b, '%s', '%s', %d, %d)", $file->name, serialize($files[$filename]->info), 'module', $file->filename, 0, $initialize);
			}
		}
		
		$files = Moduler::buildDependencies($files);
		
		return $files;
	}
	
	/**
	 * buildDependencies()
	 * 
	 * Enter description here ...
	 * 
	 * @param unknown_type $files
	 * @return Ambiguous
	 */
	public static function buildDependencies($files) 
	{
		do
		{
			$new_dependency = FALSE;
			
			foreach ($files as $filename => $file) 
			{
				$file =& $files[$filename];
				
				if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) 
				{
					foreach ($file->info['dependencies'] as $dependency_name) 
					{
						if ($dependency_name == '-circular-' || !isset($files[$dependency_name])) 
						{
							continue;
						}
						
						$files[$dependency_name]->info['dependents'][$filename] = $filename;
						$dependency = $files[$dependency_name];
						if (isset($dependency->info['dependencies']) && is_array($dependency->info['dependencies'])) 
						{
							foreach ($dependency->info['dependencies'] as $candidate) 
							{
								if (array_search($candidate, $file->info['dependencies']) === FALSE) 
								{
									if ($candidate == $filename) 
									{
										$candidate = '-circular-';
										
										if (array_search($candidate, $file->info['dependencies']) !== FALSE) 
										{
											continue;
										}
										
										Athena::setMessage($file->info['name'] . ' is part of a circular dependency. This is not supported, and you will not be able to enabled this module.', 'error');
									}
									else 
									{
										$new_dependency = TRUE;
									}
									
									$file->info['dependencies'][] = $candidate;
								}
							}
						}
					}
				}
				
				unset($file);
			}
		} while ($new_dependency);
		
		return $files;
	}
	
	/**
	 * moduleHooks()
	 * 
	 * Enter description here ...
	 * 
	 * @param string $module
	 * @param string $hook
	 * @param string $module_class
	 * @return boolean
	 */
	public static function moduleHooks($module, $hook, $module_class = '') 
	{
		if (empty($module_class)) 
		{
			$module_class = Renderer::methodForModel($module);
		}
		
		if (!athena_class_exists($module_class)) 
		{
			$loaded = Loader::import('module', $module);
			
			if (!$loaded) 
			{
				return FALSE;
			}
		}
		
		$module_object = new $module_class(MODULE_LOAD_HOOK);
		
		return $module_object->hooks($hook);
	}
}
